﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Diagnostics;

namespace ListFiles
{
    public class MasterFileTableEntry
    {
        public bool IsDirectory;

        private string filename;
        public string Filename
        {
            get { return filename; }
            set { filename = value; }
        }

        private string path;
        public string Path
        {
            get { return path; }
            set { path = value; }
        }

        private ulong parent;
        public ulong Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        public MasterFileTableEntry(string filename, ulong parent, bool isdir)
        {
            IsDirectory = isdir;
            this.filename = filename;
            this.parent = parent;
        }
    }

    class Win32
    {
        public const uint GENERIC_READ = 0x80000000;
        public const uint GENERIC_WRITE = 0x40000000;
        public const uint FILE_SHARE_READ = 0x00000001;
        public const uint FILE_SHARE_WRITE = 0x00000002;
        public const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
        public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
        public const uint OPEN_EXISTING = 3;
        public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
        public const int INVALID_HANDLE_VALUE = -1;
        public const uint FSCTL_QUERY_USN_JOURNAL = 0x000900f4;
        public const uint FSCTL_ENUM_USN_DATA = 0x000900b3;
        public const uint FSCTL_CREATE_USN_JOURNAL = 0x000900e7;

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess,
                                                  uint dwShareMode, IntPtr lpSecurityAttributes,
                                                  uint dwCreationDisposition, uint dwFlagsAndAttributes,
                                                  IntPtr hTemplateFile);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetFileInformationByHandle(IntPtr hFile,
                                                                     out BY_HANDLE_FILE_INFORMATION lpFileInformation);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool DeviceIoControl(IntPtr hDevice,
                                                      uint dwIoControlCode,
                                                      IntPtr lpInBuffer, int nInBufferSize,
                                                      out USN_JOURNAL_DATA lpOutBuffer, int nOutBufferSize,
                                                      out uint lpBytesReturned, IntPtr lpOverlapped);

        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool DeviceIoControl(IntPtr hDevice,
                                                      uint dwIoControlCode,
                                                      IntPtr lpInBuffer, int nInBufferSize,
                                                      IntPtr lpOutBuffer, int nOutBufferSize,
                                                      out uint lpBytesReturned, IntPtr lpOverlapped);

        [DllImport("kernel32.dll")]
        public static extern void ZeroMemory(IntPtr ptr, int size);

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct BY_HANDLE_FILE_INFORMATION
        {
            public uint FileAttributes;
            public FILETIME CreationTime;
            public FILETIME LastAccessTime;
            public FILETIME LastWriteTime;
            public uint VolumeSerialNumber;
            public uint FileSizeHigh;
            public uint FileSizeLow;
            public uint NumberOfLinks;
            public uint FileIndexHigh;
            public uint FileIndexLow;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct FILETIME
        {
            public uint DateTimeLow;
            public uint DateTimeHigh;
        }


        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct USN_JOURNAL_DATA
        {
            public ulong UsnJournalID;
            public long FirstUsn;
            public long NextUsn;
            public long LowestValidUsn;
            public long MaxUsn;
            public ulong MaximumSize;
            public ulong AllocationDelta;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MFT_ENUM_DATA
        {
            public ulong StartFileReferenceNumber;
            public long LowUsn;
            public long HighUsn;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct CREATE_USN_JOURNAL_DATA
        {
            public ulong MaximumSize;
            public ulong AllocationDelta;
        }

        public class USN_RECORD
        {
            public uint RecordLength;
            public ulong FileReferenceNumber;
            public ulong ParentFileReferenceNumber;
            public uint FileAttributes;
            public int FileNameLength;
            public int FileNameOffset;
            public string FileName = string.Empty;

            private const int FR_OFFSET = 8;
            private const int PFR_OFFSET = 16;
            private const int FA_OFFSET = 52;
            private const int FNL_OFFSET = 56;
            private const int FN_OFFSET = 58;

            public USN_RECORD(IntPtr p)
            {
                this.RecordLength = (uint)Marshal.ReadInt32(p);
                this.FileReferenceNumber = (ulong)Marshal.ReadInt64(p, FR_OFFSET);
                this.ParentFileReferenceNumber = (ulong)Marshal.ReadInt64(p, PFR_OFFSET);
                this.FileAttributes = (uint)Marshal.ReadInt32(p, FA_OFFSET);
                this.FileNameLength = Marshal.ReadInt16(p, FNL_OFFSET);
                this.FileNameOffset = Marshal.ReadInt16(p, FN_OFFSET);
                FileName = Marshal.PtrToStringUni(new IntPtr(p.ToInt32() + this.FileNameOffset), this.FileNameLength / sizeof(char));
            }
        }
    }

    class Volume
    {
        private const int ALLOCATION_SIZE = 1024 * 1024;

        private MasterFileTableEntry root;
        private IntPtr volumeHandle;
        private Dictionary<ulong, MasterFileTableEntry> files = new Dictionary<ulong, MasterFileTableEntry>();
        
        public Dictionary<ulong, MasterFileTableEntry> Files
        {
            get { return files; }
        }

        public void EnumerateVolume()
        {
            foreach (DriveInfo d in DriveInfo.GetDrives())
            {
                if (d.DriveType != DriveType.Fixed) continue;

                IntPtr medBuffer = IntPtr.Zero;

                try
                {
                    CreateRootHandle(d.Name);
                    CreateChangeJournal();

                    uint bytesReturned = 0;
                    Win32.USN_JOURNAL_DATA data = new Win32.USN_JOURNAL_DATA();

                    Win32.DeviceIoControl(volumeHandle,
                        Win32.FSCTL_QUERY_USN_JOURNAL,
                        IntPtr.Zero,
                        0,
                        out data,
                        Marshal.SizeOf(data),
                        out bytesReturned,
                        IntPtr.Zero);

                    Win32.MFT_ENUM_DATA med;
                    med.StartFileReferenceNumber = 0;
                    med.LowUsn = 0;
                    med.HighUsn = data.NextUsn;
                    int sizeMftEnumData = Marshal.SizeOf(med);
                    medBuffer = Marshal.AllocHGlobal(sizeMftEnumData);
                    Marshal.StructureToPtr(med, medBuffer, true);

                    EnumerateFiles(medBuffer);
                }
                finally
                {
                    if (volumeHandle.ToInt32() != Win32.INVALID_HANDLE_VALUE)
                    {
                        Win32.CloseHandle(volumeHandle);
                    }

                    if (medBuffer != IntPtr.Zero)
                    {
                        Marshal.FreeHGlobal(medBuffer);
                    }
                }
            }
        }

        private string GetPath(string drive, MasterFileTableEntry fn)
        {
            string path = "";
            MasterFileTableEntry current = fn;

            while (true)
            {
                path = Path.Combine(current.Filename, path);

                if (!files.ContainsKey(current.Parent)) break;
                if (current == root) break;

                current = files[current.Parent];
            }

            return Path.Combine(drive + Path.DirectorySeparatorChar, path);
        }

        private void CreateRootHandle(string drive)
        {
            volumeHandle = Win32.CreateFile(
                @"\\.\" + drive.Replace(@"\", ""),
                Win32.GENERIC_READ | Win32.GENERIC_WRITE,
                Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
                IntPtr.Zero,
                Win32.OPEN_EXISTING,
                0,
                IntPtr.Zero);

            Debug.Assert(volumeHandle.ToInt32() != Win32.INVALID_HANDLE_VALUE);

            Win32.BY_HANDLE_FILE_INFORMATION fi = new Win32.BY_HANDLE_FILE_INFORMATION();

            Win32.GetFileInformationByHandle(volumeHandle, out fi);
            ulong fileIndexHigh = (ulong)fi.FileIndexHigh;
            ulong indexRoot = (fileIndexHigh << 32) | fi.FileIndexLow;

            root = new MasterFileTableEntry(drive, 0, true);
        }

        private void EnumerateFiles(IntPtr buffer)
        {
            IntPtr data = Marshal.AllocHGlobal(sizeof(ulong) + ALLOCATION_SIZE);
            uint size = 0;

            Debug.Assert(data != IntPtr.Zero);

            while (false != Win32.DeviceIoControl(
                volumeHandle,
                Win32.FSCTL_ENUM_USN_DATA,
                buffer,
                Marshal.SizeOf(typeof(Win32.MFT_ENUM_DATA)),
                data,
                sizeof(ulong) + ALLOCATION_SIZE,
                out size,
                IntPtr.Zero))
            {
                IntPtr record = new IntPtr(data.ToInt32() + sizeof(long));

                while (size > 60)
                {
                    Win32.USN_RECORD usn = new Win32.USN_RECORD(record);

                    files.Add(
                        usn.FileReferenceNumber,
                        new MasterFileTableEntry(
                            usn.FileName, 
                            usn.ParentFileReferenceNumber, 
                            0 != (usn.FileAttributes & Win32.FILE_ATTRIBUTE_DIRECTORY)));

                    record = new IntPtr(record.ToInt32() + usn.RecordLength);
                    size -= usn.RecordLength;
                }

                Marshal.WriteInt64(buffer, Marshal.ReadInt64(data, 0));
            }

            Marshal.FreeHGlobal(data);
        }

        private void CreateChangeJournal()
        {
            Win32.CREATE_USN_JOURNAL_DATA data;
            data.MaximumSize = 0x800000;
            data.AllocationDelta = 0x100000;

            int dataSize = Marshal.SizeOf(data);
            IntPtr buffer = Marshal.AllocHGlobal(dataSize);
            Marshal.StructureToPtr(data, buffer, true);

            uint cb;

            bool result = Win32.DeviceIoControl(volumeHandle, Win32.FSCTL_CREATE_USN_JOURNAL,
                buffer, dataSize, IntPtr.Zero, 0, out cb, IntPtr.Zero);
            
            Debug.Assert(result);
        }
    }
}
